This notebook deals with a realistic model of the aircraft at take-off conditions. We deal with transport-type airplanes and write the general take-off equations. There are several different types of aircraft take-off. We will refer exclusively to Conventional Take-Off.
We consider a take-off operation on a vertical plane. Due to the level of approximations that it is possible to achieve, in the calculation of the take-off of a conventional airplane, we will assume that the runway reference system and the ground reference system are equivalent.
The sequence of airplane speeds during take-off are given in the figure below.
The airplane accelerates, the nose landing gear lifts off and the airplane rotates with a speed $V_R$; the airplane lifts off the ground with a speed $V_{LOF}$ and follows a rectilinear flight path to clear a conventional screen at 35 feet from the ground.
The take-off is divided into three segments:
The ground run is the distance between brake-release and lift-off of the forward wheels. To calculate the ground run, we write the dynamics equations on the centre of gravity of the aircraft in the horizontal and vertical directions, for a take-off from a horizontal runway. The engine thrust is aligned with the vector velocity.
Governing equations:
State Increments:
The thrust is given by the thrust model which takes as an input the speed.
Target: Reach $V_R$
The lift-off point is reached with at least one landing gear on the ground. The rotation of the aircraft must be done with a small angle or else there is a risk of a tail strike on the runway. The rotation run is the distance between lift-off of the forward wheels and lift-off of the main landing gears.
Governing equations:
Constant: $q = Rotation Rate$
State Increments:
Target : $W = L(\alpha)$
In this segment we will try to compute the state of the aircraft from $V_{LOF}$ up to point A.
Governing equations:
Target: Reach 35ft
Remark: In this model the incidence angle $\epsilon$ is considered to be 0.
The climb velocity in general accelerated flight is
$v_c = \frac{T-D}{W}U - \frac{U}{g}dU/dt$
Our goal is to ascend with a max rate of climb, which means minimizing the acceleration. The pilot can manage the acceleration of the aircraft by adjusting the stick input, which alters the change of $\alpha$. A simple gain control loop is the most straightforward method to simulate this process.
A simple schema of the control function is illustrated below:
# Post Processing
import pandas as pd
import numpy as np
# Plotting
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Take Off Segments
from src.modules.take_off_env import TakeOffPrep
from src.modules.ground_phase import GroundRoll
from src.modules.rotation_phase import RotationPhase
from src.modules.airborne_phase import AirbornePhase
input_variables = {
"mass": 80e3, # [kg]
"conf": "1+F",
"zp": 0, # [ft]
"lg": "Up",
"engine_state": "OEI",
"timestep": 1e-1,
}
Before initiating the TakeOff Procedure some preliminary steps have to be made first.
A dedicated class was created to prepare the take-off
example_preperation = TakeOffPrep(input_variables)
We calculate the stall speed with the following equation: $ V_{stall} = \sqrt(\frac{2 * mg}{(\rho S C_{Lmax}}) $
example_preperation.calculate_stall_speed()
print(f"Stall Speed = {round(example_preperation.v_sta, 2)}kt")
Stall Speed = 129.71kt
example_preperation.define_characteristic_speeds()
print("Characteristic Speeds\n")
for key, value in example_preperation.speeds.items():
print (f"{key} = {round(float(value), 2)}kt")
Characteristic Speeds v2min = 152.22kt vr = 136.2kt vef = 134.2kt vmca = 114.6kt v_stall = 129.71kt
In the case of non compliance of $V_{2min}$ with JAR25.121b, we take the ssg of JAR(2.4%) and we calculate the new $V_2$
example_preperation.calculate_v2_jar()
JAR Assertion: SSG of V2min 4.7% Passed
All these methods are summed up into one function in the same class called pilot_preparation.
With our characteristic speeds established, we can commence the takeoff process. The initial step is to start with the minimum $V_R$ and gradually attain $V_2$ while ascending to a height of 35ft.
If any of the flight's corresponding speeds fail to meet the criteria, we increase $V_R$ by $1\% V_{stall}$ and initiate the iterative process anew until all the criteria are satisfied.
class TakeOffProc(GroundRoll, RotationPhase, AirbornePhase):
def check_vr(self) -> None:
if self.variables["need_to_increase_Vr"]:
self.speeds["vr"] += 0.01 * self.speeds["v_stall"]
self.speeds["vef"] = self.speeds["vr"] - 2
print(f"VR increased at {self.speeds['vr']} kt")
super().initialize_data()
def takeoff(self) -> None:
super().pilot_preparation() # calculate all the characteristic speeds
print(f"Vr = {self.speeds['vr']} kt")
while self.variables["cas_kt"] < self.speeds["v_target"]:
super().up_to_rotation() # 1st segment of takeoff
super().transition_phase() # 2nd segment of takeoff
self.check_vr()
if not self.variables["need_to_increase_Vr"]:
super().airborne_phase() # 3rd segment of takeoff
self.check_vr()
This class inherits the three takeoff segments, each of which inherits the takeoff preparation class. If the $V_R$ fails to meet the takeoff criteria, we increment it by 1% of the stall speed until all the criteria are met.
amazing_aircraft = TakeOffProc(input_variables)
amazing_aircraft.takeoff()
JAR Assertion: SSG of V2min 4.7% Passed Vr = 136.20010905033593 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 137.4972529460534 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 138.7943968417709 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 140.09154073748837 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 141.38868463320586 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 142.68582852892334 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 143.98297242464082 kt Could not reach V2 Increase VR the a/c cannot reach the target :( VR increased at 145.2801163203583 kt V2min reached @ 0.18ft!
So, the algorithm finally converged to a specific $V_R$!
In order to post process the results we are going to transform the log dictionary to a pandas dataframe.
df = pd.DataFrame(amazing_aircraft.event_log)
df["accel_cas"] = np.gradient(df["cas_kt_log"], df["t_log"], edge_order=2)
df["accel_tas"] = np.gradient(df["tas_kt_log"], df["t_log"], edge_order=2)
df.shape
(390, 14)
Now we are going to check the time and speed values that the algorithm found about the characteristic speeds.
pd.DataFrame(amazing_aircraft.characteristic_instants)
| Rotation | LiftOff | v35ft | v2 | |
|---|---|---|---|---|
| Instant | 30.00 | 34.20 | 38.90 | 34.60 |
| Speed | 145.39 | 152.01 | 151.78 | 152.23 |
We can see that the rotation started 30 seconds after the break release.
Then 4 seconds later the aircraft lifts off!
It only took about 9 seconds for the aircraft to reach an altitude of 35ft!
We notice that $V_{LOF}$ is very close to $V_2$, which makes sense since the goal on the airborne phase is to utilize most of the aircraft's kinetic energy for climbing, resulting in a steep 𝑉𝑧(𝑡) curve.
Remark: There is a distinction between the values $(V_2, V_{35ft}) $. This discrepancy is caused by the controller's impact. The acceleration never reaches zero, but instead oscillates around that value. Consequently, we can anticipate slight differences in the speed values during the airborne phase.
pd.DataFrame(amazing_aircraft.speeds)
| v2min | vr | vef | vmca | v_stall | v_target | |
|---|---|---|---|---|---|---|
| 0 | 152.221103 | 145.280116 | 143.280116 | 114.5999 | 129.71439 | 152.221103 |
Remark: The $V_R$ is not exactly the same with the above results due to the timestep's sensitivity. If we reduce the timestep then the increments dx and dv should tend towards continuity, resulting in a more precise value.
fig = px.line(df, x='t_log', y= "x_log")
# Edit the layout
fig.update_layout(title='Distance in function of time',
xaxis_title='Time[s]',
yaxis_title='Distance x_ground-axis[m]')
fig.show()
The above curve has the anticipated shape, with the aircraft following a non-constant accelerating motion during the ground segment and a mostly linear motion during the airborne phase, where zero acceleration is the objective.
Specifically, the curve displays a concave-up shape up to 34 seconds, after which it tends to become more linear.
fig = px.line(df, x='t_log', y= "tas_kt_log")
fig.update_layout(title='True Air Speed in function of time',
xaxis_title='Time [s]',
yaxis_title='TAS [kt]')
fig.show()
The above graph depicts the velocity increasing in a non-linear fashion up to 34.2 seconds, which is expected since the acceleration is not constant during the ground phase. Subsequently, it stabilizes at the 𝑉2 value, which is logical given that the pilot aims to attain zero acceleration during the airborne phase to facilitate the fastest possible ascent.
fig = px.line(df, x='t_log', y= "vz_log")
fig.update_layout(title='Rate of Climb in function of time',
xaxis_title='Time [s]',
yaxis_title='Rate of Climb [m/s]')
fig.show()
As expected the rate of climb is 0 until lift-off(34.2 secs) and then we see a rapid increase due to the maximazation of SEP(Specific Excess Power). The peak of this curve marks the peak of the climb gradient, since $V_z = TAS\sin\gamma$ and TAS is quasi static.
fig = px.line(df, x='t_log', y=["alpha_log", "teta_log", "gamma_log"])
fig.update_layout(title='Aoa, teta & gamma in function of time',
xaxis_title='Time[s]',
yaxis_title='Degrees')
fig.show()
In this graph we can see that until lift-off $\alpha$ and $\theta$ have the same value since $\gamma$ is 0. When the a/c is airborne and tries to climb the value of gamma goes up. $\gamma$ is approximately given by the following formula:
$\gamma = \frac{T\cos\alpha -D}{W}$
This formula makes evident that in order to increase $\gamma$ we have to lower $\alpha$. This happens due to two main reasons.
For $\alpha$ we see that it increases until it reaches the target acceleration and then starts to decrease.
$\theta$ is calculated by the known formula :
$\theta = \alpha + \gamma$
# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add traces
fig.add_trace(
go.Scatter(x=df['t_log'], y=df["accel_tas"], name="Acceleration"),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=df['t_log'], y=df["alpha_log"], name="Angle of Attack"),
secondary_y=True,
)
# Add figure title
fig.update_layout(
title_text="Acceleration and Angle of Attack in function of time"
)
# Set x-axis title
fig.update_xaxes(title_text="Time[s]")
# Set y-axes titles
fig.update_yaxes(title_text="TASA [m/s2]", secondary_y=False)
fig.update_yaxes(title_text="Alpha [deg]", secondary_y=True)
fig.show()
In the airborne segment, the pilot uses 𝛼 to control the acceleration of the aircraft. When the aircraft is airborne with a positive acceleration, the pilot pulls the stick to increase 𝛼 and decrease the acceleration. The peak of 𝛼 is reached when the acceleration is close to zero. Subsequently, the acceleration becomes negative, and the pilot pushes the stick to decrease 𝛼 and control the acceleration close to zero.
This notebook presents the AFM OEI Take Off procedure. The user has to input the initial a/c and atmospheric conditions and then the code is able to produce an analytical decription of the take off procedure. The results are satisfying and produce an accurate description of the take off procedure. Then the user can post process the information as he likes.
In this simulation there are several factors that were not taken into account:
These factors limit the accuracy of the model